<?php
namespace cubox;

use cubox\provider\Response;
use cubox\provider\response\Ajax;
use cubox\provider\exception\LogException;
use cubox\provider\exception\DataException;


class Cubox{
    public static string $version = 'V1.0.0';
    public static string $title   = 'CuboxCMS';
    public static $instance =  array();
    private static $response ;

    /**
     * 获取应用实例
     * 
     * @param string $name 应用名称
     * @return App
     */
    public static function app(array | string $project):App{

        if (is_string($project)){
            $name = $project;
        }else{
            $name = $project['name'];
        }

        $instanceName = 'app.'.$name;

        if (!isset(self::$instance[$instanceName])){

            if (is_string($project)){
                $appInfoList = self::getAppList();

                if (!isset($appInfoList[$name])){
                    throw new \Exception("应用{$name}不存在");
                }

                $project = $appInfoList[$name];
            }
          
            
            $className = '\\app\\'.$name.'\\App';
            $app = new $className();
            $app->name = $name;
            $app->__init($project);
            $app->init();
            
            self::$instance[$instanceName] = $app;
        }

        return self::$instance[$instanceName];
    }

    public static function getAppList():array{

        static $list;
        if (isset($list)) return $list;

        $runtimePath = ROOT.DS.RUNTIME.DS.'bi.php';
        if (file_exists($runtimePath) && !CUBOX_DEBUG){
            $list  = require($runtimePath);
            return $list;
        }else{
            $path  = ROOT.DS.APP;
            $files = file_exists($path)?scandir($path):[];
            $list = [];
            
            foreach($files as $file){
                if($file == '.' || $file == '..') continue;
                $filePath = $path.DS.$file;
                if(is_dir($filePath)){
                    $project = require ($filePath.DS.'project.php');
                    if (isset($project['status']) && $project['status']){
                        $list[$file] = $project;
                    }
                }
            }

            if (!file_exists(dirname($runtimePath))) mkdir(dirname($runtimePath),0777,true);

            file_put_contents($runtimePath,'<?php'. PHP_EOL . 'return ' .var_export($list,true).';') ;
            if (function_exists('opcache_reset')) opcache_reset();
            return $list;
        }
    }


    public static function init(){
         //自动加载

         self::$response = Response::class;

         if(isset($_SERVER["HTTP_X_REQUESTED_WITH"]) && strtolower($_SERVER["HTTP_X_REQUESTED_WITH"])=="xmlhttprequest"){
            self::$response = Ajax::class;
         }
    }

    public static function response(string $response){
        if (in_array(Response::class,class_parents($response))){
            self::$response = $response;
        }else{
            throw new \Exception('Response class not exists:'.$response);
        }
    }

    /**
     * 运行应用
     */
    public static function run(string $urlPath=''){
        
        try{
            $data = self::route($urlPath);
            self::$response::write($data);
        }catch(LogException $e){
            self::log($e->getFile().':'.$e->getLine().' '.$e->getMessage());
            self::$response::error($e->getMessage(),$e);
        }catch(DataException $e){
            self::$response::error($e->getMessage(),$e->getData());
        }catch(\Exception $e){
            self::$response::error($e->getMessage(),$e);
        }
    }

    public static function route(string $urlPath=''){
        $urlPath || $urlPath = self::getPathInfo();
        
        if ($urlPath!='' &&  $urlPath!='/'){
            $router = Route::parse($urlPath);
        }else{
            $cuboxConfig = self::config('system');
            if ($cuboxConfig->exists('default_app')){
                $router = [
                    'app' => $cuboxConfig->default_app,
                    'controller' => 'index',
                    'action' => 'index',
                    'gets' => []
                ];
            }else{
                throw new \Exception('No default app.');    
            }   
        }

        $app = self::app($router['app']);

        return $app->run($router['controller'],$router['action'],$router['gets']);
    }
    
    /**
     * 获取配置实例
     * 
     * @param string $name 配置名称
     * @return Data
     */
    public static function config(string $name):Data{

        $instanceName  = md5('config.cubox.'.$name);
        
        if (!isset(self::$instance[$instanceName])){
            $name = str_replace('.',DS,$name);
            self::$instance[$instanceName] = new Data(ROOT.DS.CONFIG.DS.$name.'.php');
        }
        
        return self::$instance[$instanceName];
    }

    private static function getDriver(string $type,string |null $name = '',Data $config = null,string $title = ''){

        if ($title == '') $title =  $type;
        if (is_null($config) || $config->empty()){
            $config = self::config($type);
        }

        if ($name=='' || $name =='default'){
            if (isset($config->default)){
                $driverConfigName = $config->default;
            }else{
                throw new \Exception("{$title}的{$name}配置不存在");
            }
        }else{
            $driverConfigName = $name;
        }


        $driverConfig = $config->$driverConfigName;
        
        if (is_null($driverConfig)){
            throw new \Exception("{$title}的{$driverConfigName}配置不存在");
        }

        if (!isset($driverConfig['driver'])){
            throw new \Exception("{$title}的{$driverConfigName}配置错误");
        }

        $instanceName  = 'db.'.md5(json_encode($driverConfig));
        if (isset(self::$instance[$instanceName])){
            return self::$instance[$instanceName];
        }

        $driverName = $driverConfig['driver'];
        $className  = '\\cubox\\'.$type.'\\'.ucfirst($driverName);

        if (class_exists($className)){
            $driver =  new $className($driverConfig);
            if (get_parent_class($driver) == 'cubox\\'.ucfirst($type)){
                self::$instance[$instanceName] = $driver;
                return $driver;
            }else{
                throw new \Exception("{$title}的{$driverName}驱动不符合规范");
            }
        }else{
            throw new \Exception("{$title}的{$driverName}驱动不存在");
        }
    }


    /**
     * 获取数据库实例
     * 
     * @param Data $data 数据库配置
     * @return Database
     */
    public static function db(string $name = '',Data $config = null):Database{
        return self::getDriver('database',$name,$config,'数据库');
    }
    
    /**
     * 获取缓存实例
     * 
     * @param Data $data 缓存配置
     * @return Cache
     */
    public static function cache(string $name = null,Data $config = null):Cache{
        return self::getDriver('cache',$name,$config,'缓存');
    }

    /**
     * 获取原始的路径信息
     *
     * @return string
     */
    private static function getPathInfo():string{
        $filename = '/'.pathinfo($_SERVER['SCRIPT_FILENAME'],PATHINFO_FILENAME).'.'.pathinfo($_SERVER['SCRIPT_FILENAME'],PATHINFO_EXTENSION);

        if (isset( $_SERVER['REQUEST_URI']) && substr($_SERVER['REQUEST_URI'],0,strlen($filename))==$filename){
            $path =  substr($_SERVER['REQUEST_URI'],strlen($filename));
        }else{
            $path =  $_SERVER['REQUEST_URI'];    
        }

        if (strpos($path,'?')!==false){
            $path = substr($path,0,strpos($path,'?'));
        }

        return $path;
    } 

    /**
     * 获取网站的根URL
     *
     * @return string
     */
    public static function baseUrl():string{
        $filename = '/'.pathinfo($_SERVER['SCRIPT_FILENAME'],PATHINFO_FILENAME).'.'.pathinfo($_SERVER['SCRIPT_FILENAME'],PATHINFO_EXTENSION);

        if (isset($_SERVER['REQUEST_URI']) && substr($_SERVER['REQUEST_URI'],0,strlen($filename)) == $filename){
            return  $filename.'/';
        }else{
            return  '/';
        }
    }

    public static function url(string $app,string $controller,string $action,array $gets=array()):string{

        $alias = self::config('alias');
        $app = $alias->exists($app) && $alias->$app!='' ?$alias->$app:$app;
        $path = self::baseUrl().'app/'.$app.'/'.$controller.'/'.$action;

        if ($gets){
            foreach($gets as $k=>$v) $path .= '/'.$k.'/'.$v;
        }

        return $path;
    }

    public static function go(string $url){
        if (!headers_sent())
            header('Location: '.$url);
        else {
            echo '<script type="text/javascript">';
            echo 'window.location.href="'.$url.'";';
            echo '</script>';
            echo '<noscript>';
            echo '<meta http-equiv="refresh" content="0;url='.$url.'" />';
            echo '</noscript>';
        }
    }


    public static function debug(string $content){
        static $prevTime;

        if (!$prevTime && defined('STAETTIME')) $prevTime = STAETTIME; 
        
        if (defined('CUBOX_DEBUG') && CUBOX_DEBUG || true){
            $dirPath =  ROOT.DS.RUNTIME.DS.'debug';

            if (!is_dir($dirPath)){
                mkdir($dirPath,0777,true);
            }
            if (!is_writable($dirPath)){
                throw new \Exception('日志目录不可写');
            }
    
            file_put_contents($dirPath.DS.date('Y-m-d').'.log',date('H:i:s').' '.round(microtime(true)-$prevTime,4).' '.$content."\n",FILE_APPEND | LOCK_EX);
            $prevTime = microtime(true);
        }
        
    }
    
    public static function log(string $content){
        $dirPath = ROOT.DS.RUNTIME.DS.'log'.DS;
        if (!is_dir($dirPath)){
            mkdir($dirPath,0777,true);
        }
        if (!is_writable($dirPath)){
            throw new \Exception('日志目录不可写');
        }
        file_put_contents($dirPath.DS.date('Y-m-d').'.log',date('H:i:s').' '.$content."\n",FILE_APPEND | LOCK_EX);
    }

    /**
     * 获取插件
     * 
     * @param string $name  插件名称
     * @param array $appList 支持的APP列表
     * @return array
     */
    
    public static function plug(string $name,array $appList = []):array{

        $apps = self::getAppList();

        
        $list = [];
        foreach($apps as $app=>$info){
            if (empty($appList) || in_array($app,$appList)){
                if (isset($info['plug']) && isset($info['plug'][$name]) &&  isset($info['plug'][$name]['type']) && $info['plug'][$name]['type']=='php'){
                    $list[] = $app;
                }
            }
        }
 
        $re = [];
        foreach($list as $app){
            try{
                $className = '\\app\\'. $app. '\\plug\\'. $name;
                $re [$app]= new $className();
            }catch(\Throwable $e){
                
            }
        }

        return $re;
    }
}